/*
 * @Author: liuxinye
 * @Date: 2022-01-17 18:18:03
 * @LastEditors: liuxinye
 * @LastEditTime: 2022-08-08 16:57:25
 * @Description: 机器人动画
 */
<template>
</template>
<script setup>
import * as THREE from 'three';
import Stats from 'three/examples/jsm/libs/stats.module'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { ref, reactive } from 'vue';

let container, stats, clock, gui, mixer, actions, activeAction, previousAction;
let camera, scene, renderer, model, face;
const api = { state: 'Walking' };
//通过这个例子可以了解到
//相机、场景、光照、Mesh、BufferGeometry、MeshPhongMaterial、GLTFLoader、渲染器、Stats等知识点
init();
animate();
function init() {
    if (!container) {
        container = document.createElement('div');
        document.body.appendChild(container);
    }
    //创建透视相机
    camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.5, 200);
    camera.position.set(-5, 3, 10);
    camera.lookAt(new THREE.Vector3(0, 2, 0));

    scene = new THREE.Scene();//创建场景
    scene.background = new THREE.Color(0xa0a0a0);//背景色
    // scene.fog = new THREE.Fog(0xa0a0a0, 20, 100);//前景色

    clock = new THREE.Clock();

    //创建和添加光照
    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
    hemiLight.position.set(0, 20, 0);
    scene.add(hemiLight);

    const dirLight = new THREE.DirectionalLight(0xffffff);
    dirLight.position.set(0, 20, 10);
    scene.add(dirLight);
    //mesh网格类，代表多边形网格，接收geometry(形状)和material(材质)为参数
    //bufferGemetry
    // const geometry = new THREE.BufferGeometry();
    // // 创建一个简单的矩形. 在这里我们左上和右下顶点被复制了两次。
    // // 因为在两个三角面片里，这两个顶点都需要被用到。
    // const vertices = new Float32Array( [
    //     -1.0, -1.0,  1.0,
    //     1.0, -1.0,  1.0,
    //     1.0,  1.0,  1.0,

    //     1.0,  1.0,  1.0,
    //     -1.0,  1.0,  1.0,
    //     -1.0, -1.0,  1.0
    // ] );

    // // itemSize = 3 因为每个顶点都是一个三元组。
    // geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
    // const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
    // const mesh = new THREE.Mesh( geometry, material );
    //PlaneGeometry 一个生成平面的几何体类，一般用于舞台地板的搭建，是MeshPhongMaterial一种用于具有镜面高光的光泽表面的材质。
    const mesh = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), new THREE.MeshPhongMaterial({
        color: 0x111111, depthWrite: false
    }));
    mesh.rotation.x = -Math.PI*2 ; //测试
    scene.add(mesh);

    //加载模型
    const loader = new GLTFLoader();
    loader.load('models/RobotExpressive.glb', function (gltf) {
        model = gltf.scene;
        scene.add(model);
        createGUI(model, gltf.animations);
    }, undefined, (e) => {
        console.error(e);
    })

    //创建渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true })
    renderer.setPixelRatio(window.devicePixelRatio);//设置设备像素比
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.outputEncoding = THREE.sRGBEncoding;//定义渲染器的输出编码
    container.appendChild(renderer.domElement);
    window.addEventListener('resize', onWindowResize)
    //
    stats = new Stats();
    container.appendChild(stats.dom);

}
//使用GUI创建控制控件
function createGUI(model, animations) {
    const states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing'];
    const emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp'];

    gui = new GUI();
    //动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时，每个对象都可以使用同一个动画混合器。
    mixer = new THREE.AnimationMixer(model);

    actions = {};

    for (let i = 0; i < animations.length; i++) {

        const clip = animations[i];//从模型中获取到的每个动画对象
        const action = mixer.clipAction(clip);//根据动画创建一个动作AnimationAction，用于我们控制动画
        actions[clip.name] = action;
        //设置动画是否可循环
        if (emotes.indexOf(clip.name) >= 0 || states.indexOf(clip.name) >= 4) {

            action.clampWhenFinished = true;//动画执行完最后一帧后是否暂停
            action.loop = THREE.LoopOnce;//循环方式

        }

    }

    // states

    const statesFolder = gui.addFolder('States');

    const clipCtrl = statesFolder.add(api, 'state').options(states);

    clipCtrl.onChange(function () {

        fadeToAction(api.state, 0.5);

    });

    statesFolder.open();

    // emotes

    const emoteFolder = gui.addFolder('Emotes');

    function createEmoteCallback(name) {

        api[name] = function () {

            fadeToAction(name, 0.2);

            mixer.addEventListener('finished', restoreState);

        };

        emoteFolder.add(api, name);

    }

    function restoreState() {

        mixer.removeEventListener('finished', restoreState);

        fadeToAction(api.state, 0.2);

    }

    for (let i = 0; i < emotes.length; i++) {

        createEmoteCallback(emotes[i]);

    }

    emoteFolder.open();

    // expressions

    face = model.getObjectByName('Head_4');

    const expressions = Object.keys(face.morphTargetDictionary);
    const expressionFolder = gui.addFolder('Expressions');

    for (let i = 0; i < expressions.length; i++) {

        expressionFolder.add(face.morphTargetInfluences, i, 0, 1, 0.01).name(expressions[i]);

    }

    activeAction = actions['Walking'];
    activeAction.play();

    expressionFolder.open();

}
//动作动画过渡
function fadeToAction(name, duration) {

    previousAction = activeAction;
    activeAction = actions[name];

    if (previousAction !== activeAction) {

        previousAction.fadeOut(duration);

    }

    activeAction
        .reset()
        // .setEffectiveTimeScale(1)
        // .setEffectiveWeight(1)
        // .fadeIn(duration)
        .play();

}

function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);

}

//

function animate() {

    const dt = clock.getDelta();

    if (mixer) mixer.update(dt);

    requestAnimationFrame(animate);

    renderer.render(scene, camera);

    stats.update();

}

</script>
 
<style  scoped>
body {
    color: #222;
}

a {
    color: #2fa1d6;
}

p {
    max-width: 600px;
    margin-left: auto;
    margin-right: auto;
    padding: 0 2em;
}
</style>
 